# coding: utf-8
import logging
import atx
import os
import zipfile
import os.path
import time
import cv2
import sys
import json

logging.basicConfig(
    level=logging.DEBUG,
    format="[%(asctime)s] %(name)s:%(levelname)s: %(message)s"
)


def get_package_name(apk_path):
    apk_INFO = os.popen('aapt dump badging ' + apk_path).read()
    package_name = apk_INFO[apk_INFO.find("package: name='") + 15:apk_INFO.find("versionCode") - 2]
    return package_name


def get_device_resolution():
    device_INFO = os.popen('adb shell dumpsys window displays').read()
    device_resolution = device_INFO[device_INFO.find("cur=") + 4:device_INFO.find("app=") - 1].split('x')
    return device_resolution


def init_atx():
    try:
        d = atx.connect(os.getenv("SERIAL"))
        logging.info('Initiate atx success !')
        return d
    except:
        logging.error('Initiate atx fail !')
        return False


def install_apk(d, apk_path):
    try:
        d.adb_cmd(['install', apk_path])
        logging.info('Install apk success !')
        return True
    except:
        logging.error('Install apk fail !')
        return False


def start_app(d, package_name):
    try:
        d.start_app(package_name)
        logging.info('Start apk success !')
        return True
    except:
        logging.error('Start apk fail !')
        return False


def stop_app(d, package_name):
    d.stop_app(package_name, clear=True)


def restart_app(d, package_name):
    stop_app(d, package_name)
    time.sleep(2)
    start_app(d, package_name)
    time.sleep(2)


def uninstall_app(package_name):
    os.popen('adb uninstall ' + package_name)


def get_current_activity(d):
    # return str(d.current_app().activity)
    apk_INFO = os.popen('adb shell dumpsys activity | findstr "mFocusedActivity"').read()
    package_name = apk_INFO[apk_INFO.find("/") + 1:apk_INFO.rfind(" ")]
    return package_name



def check_current_app(d, package_name):
    if package_name == str(d.current_app().package):
        return True
    else:
        return False


def unzip_apk(apk_path):
    try:
        z = ZFile(apk_path)
        z.extract_to(TEMP_PATH + apk_path[apk_path.rfind('\\') + 1:])
        z.close()
        logging.info('Unzip apk success !')
    except:
        logging.error('Unzip apk fail !')


def read_resImage(apk_path):
    image_dict = {}
    rootdir = TEMP_PATH + apk_path[apk_path.rfind('\\') + 1:] + '\\'
    for parent, dirnames, filenames in os.walk(rootdir):
        for filename in filenames:
            file_path = os.path.join(parent, filename)
            if file_path.endswith('png'):
                image_dict[file_path] = 0
    if len(image_dict) < 10:
        logging.error('%d image files founded, less than 10 !', len(image_dict))
        logging.error('RESOURCE FILE UNSATISFIED, HALTING !')
        sys.exit()
    else:
        logging.info('%d image files founded !', len(image_dict))
    return image_dict


def get_screenshot(d, apk_path, screenshot_name):
    try:
        screenshot_path = RESULT_PATH + '\\' + apk_path[apk_path.rfind('\\') + 1:] + '\\'
        d.screenshot(screenshot_path + str(screenshot_name) + '.png')
        logging.info('Take screenshot success !')
    except:
        logging.info('Take screenshot fail !')


# def match_image(d, image_dict):
#     d.keep_screen()
#     logging.info('Start matching ...')
#     logging.info('Current activity is ' + get_current_activity(d))
#     for image in image_dict.keys():
#         logging.info(image)
#         match = None
#         try:
#             match = d.exists(image, method='sift')
#         except:
#             logging.error('CV error !')
#         if match != None:
#             image_dict[image] = match.pos
#     d.free_screen()
#     match_dict = {}
#     for item in image_dict.items():
#         if item[1] != 0:
#             match_dict[item[0]] = item[1]
#     logging.info('Matching done ! Positions count : %d', len(match_dict))
#     pos_dict = {}
#     for k, v in match_dict.items():
#         pos_dict[(k, v[0], v[1])] = 0
#     return pos_dict

def match_image(d, image_dict):
    d.keep_screen()
    logging.info('Start matching ...')
    logging.info('Current activity is ' + get_current_activity(d))
    for image in image_dict.keys():
        logging.info(image)
        match = None
        try:
            match = d.match(image, method='sift')

        except:
            logging.error('CV error !')
        if match != None:
            threshold = match.confidence[0] * 1.0 / match.confidence[1]
        if match != None and threshold >= 0.35:
            image_dict[image] = match.pos
    d.free_screen()
    match_dict = {}
    for item in image_dict.items():
        if item[1] != 0:
            match_dict[item[0]] = item[1]
    logging.info('Matching done ! Positions count : %d', len(match_dict))
    pos_dict = {}
    for k, v in match_dict.items():
        pos_dict[(k, v[0], v[1])] = 0
    return pos_dict


def image_filter(image_dict):
    image_dict_filtered = {}
    for img in image_dict.keys():
        try:
            size = cv2.imread(img).shape
        except:
            continue
        p_f = 0
        if size[0] >= size[1]:
            proportion = float(size[0]) / float(size[1])
        else:
            proportion = float(size[1]) / float(size[0])
        if proportion < 2.0:
            p_f = 1
        # if size[0] > 45 and size[1] > 45 and size[0] <= 355 and size[1] <= 355:
        #     image_dict_filtered[img] = 0
        if True:
            image_dict_filtered[img] = 0
    logging.info('%d image files founded after filtering !', len(image_dict_filtered))
    return image_dict_filtered


def click_back(d):
    d.adb_shell(['input', 'keyevent', '4'])
    logging.info('CLICK BACK !')
    time.sleep(3)
    logging.debug('--------' + get_current_activity(d))
    return get_current_activity(d)


def generalized_click(d, p):
    gc = []
    gc.append(get_current_activity(d))
    d.click(p[0], p[1])
    logging.info('CLICK %d,%d !', p[0], p[1])
    time.sleep(3)
    gc.append(['click', p])
    gc.append(get_current_activity(d))
    logging.debug('--------' + gc[0] + '-->' + gc[2])
    return gc


class ZFile(object):
    def __init__(self, filename, mode='r', basedir=''):
        self.filename = filename
        self.mode = mode
        if self.mode in ('w', 'a'):
            self.zfile = zipfile.ZipFile(filename, self.mode, compression=zipfile.ZIP_DEFLATED)
        else:
            self.zfile = zipfile.ZipFile(filename, self.mode)
        self.basedir = basedir
        if not self.basedir:
            self.basedir = os.path.dirname(filename)

    def addfile(self, path, arcname=None):
        path = path.replace('//', '/')
        if not arcname:
            if path.startswith(self.basedir):
                arcname = path[len(self.basedir):]
            else:
                arcname = ''
        self.zfile.write(path, arcname)

    def addfiles(self, paths):
        for path in paths:
            if isinstance(path, tuple):
                self.addfile(*path)
            else:
                self.addfile(path)

    def close(self):
        self.zfile.close()

    def extract_to(self, path):
        for p in self.zfile.namelist():
            self.extract(p, path)

    def extract(self, filename, path):
        if not filename.endswith('/'):
            f = os.path.join(path, filename)
            dir = os.path.dirname(f)
            if not os.path.exists(dir):
                os.makedirs(dir)
            file(f, 'wb').write(self.zfile.read(filename))


def dict_to_json_file(l, path):
    with open(path + '\\activity.json', 'w') as json_file:
        for k, v in l.items():
            json_file.write(str(k) + ':' + str(v) + '\n')


def list_to_json_file(l, path):
    with open(path + '\\transition.json', 'w') as json_file:
        for i in l:
            json_file.write(str(i) + '\n')


# def start_test_oneapk(apk_path):
#     atx = init_atx()
#     unzip_apk(apk_path)
#     package_name = get_package_name(apk_path)
#     start_app(atx, package_name)
#     time.sleep(5)
#     if not check_current_app(atx, package_name):
#         click_back(atx)
#         start_app(atx, package_name)
#     image = read_resImage(apk_path)
#     image = image_filter(image)
#     get_screenshot(atx, apk_path, 'main')
#     image = match_image(atx, image)
#     # start traversing
#     activity_dict = {}  # 用于存储activity页面和可点击区域
#     activity_dict[get_current_activity(atx)] = 0
#     transition_list = []  # 用于存储activity之间转换的动作
#     for img, p in image.items():
#         click_result = generalized_click(atx, p)
#         if click_result[0] == click_result[2]:
#             continue
#         else:
#             activity_dict[click_result[2]] = 1
#             click_back(atx)
#     print activity_dict


def get_filtered_image(apk_path):
    image = read_resImage(apk_path)
    image = image_filter(image)
    return image


# def click_transition(image):
#     valid_image = {}
#     transition_list = []
#     for img, p in image.items():
#         click_result = generalized_click(atx, p)
#         if click_result[0] == click_result[2]:
#             continue
#         else:
#             transition_list.append([click_result[0], click_result[2], click_result[1]])
#             valid_image[img] = p
#             click_back(atx)
#     return valid_image, transition_list

# def check_acticity_dict(activity_dict):
#     for k, v in activity_dict.items():
#         if v[2] == 0:
#             return True
#     return False



def AdaTraverse(apk_path):
    activity_dict = {}
    transition_list = []
    # stage 1 : init
    atx = init_atx()
    unzip_apk(apk_path)
    package_name = get_package_name(apk_path)
    logging.info('APP TESTING: ' + apk_path)
    logging.info('APP package_name: ' + package_name)
    install_apk(atx, apk_path)
    stop_app(atx, package_name)
    start_app(atx, package_name)
    time.sleep(5)
    for i in range(5):
        if check_current_app(atx, package_name):
            break
        else:
            start_app(atx, package_name)
            time.sleep(5)
    if not check_current_app(atx, package_name):
        logging.critical('Starting app fail !')
        sys.exit()
    res_img = get_filtered_image(apk_path)
    # stage 2 : get first activity
    # img = get_filtered_image(apk_path)
    # image_matched_1 = match_image(atx, img)
    # activity_dict[get_current_activity(atx)] = [[i[0], i[1], 0] for i in image_matched_1.values()]
    # for img, p in image_matched_1.items():
    #     click_result = generalized_click(atx, p)
    #     if click_result[0] == click_result[2]:
    #         activity_dict[click_result[0]].remove([click_result[1][1][0], click_result[1][1][1], 0])
    #         continue
    #     else:
    #         transition_list.append([click_result[0], click_result[2], click_result[1]])
    #         valid_image[img] = p
    #         click_back(atx)
    # img = get_filtered_image(apk_path)
    # image_matched_1 = match_image(atx, img)
    # pos_dict_1 = {}
    # for k, v in image_matched_1.items():
    #     pos_dict_1[(v[0]), v[1]] = 0
    # activity_dict[get_current_activity(atx)] = pos_dict_1
    # for k, v in activity_dict[get_current_activity(atx)]:
    #     click_result = generalized_click(atx, k)
    #     if click_result[0] == click_result[2]:
    #         activity_dict[click_result[0]][k] = 1
    #         continue
    #     else:
    #         transition_list.append([click_result[0], click_result[2], click_result[1]])
    #         activity_dict[click_result[0]][k] = 1
    #         click_back(atx)
    # first activity...
    image_matched_1 = match_image(atx, res_img)
    activity_1 = get_current_activity(atx)
    activity_dict[activity_1] = image_matched_1  # k:activity name v:dict[(image,p1,p2):label_clicked]
    get_screenshot(atx, apk_path, activity_1)
    # start click...
    c1 = 0
    for k, v in activity_dict[activity_1].items():
        if get_current_activity(atx) != activity_1:
            restart_app(atx, package_name)
        if v == 0:
            click_result = generalized_click(atx, [k[1], k[2]])
            if click_result[0] == click_result[2]:
                activity_dict[activity_1][k] = 1
            else:  # activity new
                c1 += 1
                activity_dict[activity_1][k] = 1
                # put into transition list
                transition_list.append(click_result)
                if click_result[2] not in activity_dict.keys():
                    callback_new_activity_founded(atx, res_img, activity_dict, transition_list, apk_path)
                else:
                    callback_old_activity_reached(atx, res_img, activity_dict, transition_list, apk_path)
    # for miss click
    # print(activity_dict)
    # print(transition_list)

    # restart_app(atx, package_name)
    miss_click_act = get_miss_click_activity(activity_dict)
    if miss_click_act != None:
        print('Starting deal missing click !')
    halt_count=0
    while miss_click_act != None:
        restart_app(atx, package_name)
        print 'Current: ' + get_current_activity(atx)
        print 'Target: ' + miss_click_act
        if (get_current_activity(atx) == miss_click_act):
            callback_old_activity_reached(atx, res_img, activity_dict, transition_list, apk_path)
        r = goto_miss_click_activity(atx, get_current_activity(atx), miss_click_act, transition_list)
        if halt_count==5:
            break
        if r == False:
            restart_app(atx, package_name)
            halt_count+=1
            continue
        else:
            callback_old_activity_reached(atx, res_img, activity_dict, transition_list, apk_path)
        miss_click_act = get_miss_click_activity(activity_dict)
    print(activity_dict)
    print(transition_list)
    dict_to_json_file(activity_dict, RESULT_PATH + '\\' + apk_path[apk_path.rfind('\\') + 1:] + '\\')
    list_to_json_file(transition_list, RESULT_PATH + '\\' + apk_path[apk_path.rfind('\\') + 1:] + '\\')
    stop_app(atx, package_name)
    uninstall_app(package_name)


def goto_miss_click_activity(atx, first_act, target_act, transition_list):
    # target_act = get_miss_click_activity(activity_dict)
    if target_act == None:
        logging.info('All activity tarversed, halting !')
        return False
    path = search_path(transition_list, first_act, target_act)
    path.reverse()
    for p in path:
        if p[1] == 'back':
            click_back(atx)
        else:
            generalized_click(atx, p[1][1])
    if get_current_activity(atx) == target_act:
        logging.info('Target activity reached !')
        return True
    else:
        logging.info('Target activity unreached !')
        return False


def get_miss_click_activity(activity_dict):
    for act, p_d in activity_dict.items():
        for pos, clicked in p_d.items():
            if clicked == 0:
                return act
    return None


def search_path(transition_list, ac1, ac2):
    path = []
    for p in transition_list:
        if p[2] == ac2:
            path.append(p)
            break
    if path[-1][0] == ac1:
        return path
    for i in range(len(transition_list)):
        if path[-1][0] == ac1:
            return path
        for p in transition_list:
            if p[2] == path[-1][0]:
                path.append(p)
                break
    return False


def callback_old_activity_reached(atx, res_img, activity_dict, transition_list, apk_path):
    current_activity = get_current_activity(atx)
    logging.info('Old activity reached : ' + current_activity)
    for k, v in activity_dict[current_activity].items():
        # back
        a = get_current_activity(atx)
        if a != current_activity:
            b = click_back(atx)
            if b == a:
                break
            transition_list.append([a, 'back', b])
            if b != current_activity:
                if b not in activity_dict.keys():
                    callback_new_activity_founded(atx, res_img, activity_dict, transition_list, apk_path)
                else:
                    callback_old_activity_reached(atx, res_img, activity_dict, transition_list, apk_path)
                break
        if v == 0:
            click_result = generalized_click(atx, [k[1], k[2]])
            if click_result[0] == click_result[2]:
                activity_dict[current_activity][k] = 1
            else:  # activity new
                activity_dict[current_activity][k] = 1
                # put into transition list
                transition_list.append(click_result)
                if click_result[2] not in activity_dict.keys():
                    callback_new_activity_founded(atx, res_img, activity_dict, transition_list, apk_path)
                else:
                    callback_old_activity_reached(atx, res_img, activity_dict, transition_list, apk_path)


def callback_new_activity_founded(atx, res_img, activity_dict, transition_list, apk_path):
    current_activity = get_current_activity(atx)
    if check_current_app(atx,get_package_name(apk_path))==False:
        restart_app(atx,get_package_name(apk_path))
        return
    get_screenshot(atx, apk_path, current_activity)
    logging.info('New activity founded : ' + current_activity)
    image_matched = match_image(atx, res_img)
    activity_dict[current_activity] = image_matched
    for k, v in activity_dict[current_activity].items():
        # back
        a = get_current_activity(atx)
        if a != current_activity:
            b = click_back(atx)
            if b == a:
                break
            transition_list.append([a, 'back', b])
            if b != current_activity:
                if b not in activity_dict.keys():
                    callback_new_activity_founded(atx, res_img, activity_dict, transition_list, apk_path)
                else:
                    callback_old_activity_reached(atx, res_img, activity_dict, transition_list, apk_path)
                break
        if v == 0:
            click_result = generalized_click(atx, [k[1], k[2]])
            if click_result[0] == click_result[2]:
                activity_dict[current_activity][k] = 1
            else:  # activity new
                activity_dict[current_activity][k] = 1
                # put into transition list
                transition_list.append(click_result)
                if click_result[2] not in activity_dict.keys():
                    callback_new_activity_founded(atx, res_img, activity_dict, transition_list, apk_path)
                else:
                    callback_old_activity_reached(atx, res_img, activity_dict, transition_list, apk_path)


TEMP_PATH = 'D:\\test\\temp\\'
RESULT_PATH = 'D:\\test\\result3\\'
AdaTraverse('D:\\PaperW\\Stage3\\data\\vaguegame\\1987BCACBD6BFC26A568AA2D2230461D.apk')

# batch test
# path = r'D:\PaperW\Stage3\data\vaguegame'
# for i in os.listdir(path):
#     if i.endswith('apk'):
#         apk_path = path + '\\' + i
#         print apk_path
#         try:
#             AdaTraverse(apk_path)
#         except:
#             continue
